/*
 * Copyright (c) 2016, Roman Meyta <theshrodingerscat@gmail.com>
 * Copyright (c) 2020-2021 https://gitee.com/fsfzp888
 * All rights reserved
 */

#include <QComboBox>
#include <QDir>
#include <QFileDialog>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QImage>
#include <QLabel>
#include <QLineEdit>
#include <QPainter>
#include <QPushButton>
#include <QSizePolicy>
#include <QSplitter>
#include <QString>
#include <QTime>
#include <QVBoxLayout>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>

#include "ImageFormats.h"
#include "VideoCapture.h"
#include "VideoDevice.h"
#include "WebcamWindow.h"

WebcamWindow::WebcamWindow(QWidget *parent)
    : QMainWindow(parent),
      m_viewport(new QLabel),
      m_frameMutex(),
      m_frame(),
      m_controlLayout(new QVBoxLayout),
      m_controlGroup(new QGroupBox),
      m_windowLayout(new QHBoxLayout),
      m_windowGroup(new QGroupBox),
      m_startButton(new QPushButton(tr("Turn On"))),
      m_stopButton(new QPushButton(tr("Turn Off"))),
      m_captureButton(new QPushButton(tr("Capture"))),
      m_captureThreeButton(new QPushButton(tr("Capture Three"))),
      m_devicesLabel(new QLabel(tr("Devices"))),
      m_devices(new QComboBox),
      m_resolutionsLabel(new QLabel(tr("Resolutions"))),
      m_resolutions(new QComboBox),
      m_directoryLabel(new QLabel(tr("Output Path"))),
      m_directory(new QLineEdit),
      m_browserButton(new QPushButton(tr("Browser"))),
      m_nameLabel(new QLabel(tr("name"))),
      m_name(new QLineEdit),
      m_browserDirectoryLayout(new QHBoxLayout),
      m_devicesGroup(new QGroupBox),
      m_devicesLayout(new QVBoxLayout),
      m_vsplitter(new QSplitter),
      //m_flipButton(new QPushButton(tr("Flip frame"))),
      m_videoCapture(nullptr),
      m_appDirPath(QDir::currentPath()),
      m_aviWriter(new avilib::AviWriter),
      m_settings("Z Vision Tech", "UVCapture"),
      m_photoCount(0),
      m_isCapturing(false),
      m_isStop(true),
      m_isFlipped(false)
{

    setWindowTitle(tr("Webcam"));
    setWindowFlags(this->windowFlags() | Qt::MaximizeUsingFullscreenGeometryHint);

    m_appDirPath = m_settings.value("mainwindow/directory", m_appDirPath).toString();
    m_directory->setText(m_appDirPath);
    m_directory->setReadOnly(true);
    m_directory->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);

    m_devicesLayout->addWidget(m_devicesLabel);
    m_devicesLayout->addWidget(m_devices);
    m_devicesLayout->addWidget(m_resolutionsLabel);
    m_devicesLayout->addWidget(m_resolutions);

    m_browserDirectoryLayout->addWidget(m_directory, 3);
    m_browserDirectoryLayout->addWidget(m_browserButton, 1);

    m_devicesLayout->addWidget(m_directoryLabel);
    m_devicesLayout->addLayout(m_browserDirectoryLayout);

    m_devicesLayout->addWidget(m_nameLabel);
    m_devicesLayout->addWidget(m_name);

    m_devicesGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    m_devicesGroup->setLayout(m_devicesLayout);

    m_controlLayout->addWidget(m_devicesGroup);
    m_controlLayout->addWidget(m_vsplitter);
    m_controlLayout->addWidget(m_captureButton);
    m_controlLayout->addWidget(m_captureThreeButton);
    m_controlLayout->addWidget(m_startButton);
    m_controlLayout->addWidget(m_stopButton);
    //m_controlLayout->addWidget(m_flipButton);
    m_controlGroup->setLayout(m_controlLayout);
    m_controlGroup->setMinimumWidth(300);
    m_controlGroup->setMaximumWidth(300);

    m_stopButton->setEnabled(false);
    m_captureButton->setEnabled(false);
    m_captureThreeButton->setEnabled(false);
    //m_flipButton->setEnabled(false);

    m_viewport->setMinimumSize(640, 480);
    m_viewport->setBackgroundRole(QPalette::Base);
    m_viewport->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    m_viewport->setScaledContents(true);
    m_windowLayout->addWidget(m_viewport);
    m_windowLayout->addWidget(m_controlGroup);
    //m_windowLayout->setSizeConstraint(QLayout::SetFixedSize);
    m_windowGroup->setLayout(m_windowLayout);
    setCentralWidget(m_windowGroup);

    m_videoCapture = new VideoCapture([this](unsigned char *data, int len, VideoDevice *device) { processFrame(data, len, device); },
                                      [this](unsigned char *data, int len, VideoDevice *device) {
                                          if (!m_isStop && m_isCapturing)
                                          {
                                              processStillFrame(data, len, device);
                                          }
                                      });

    auto devicesNames = m_videoCapture->getDevicesNames();
    for (auto &deviceName : devicesNames)
    {
        QString name = QString::fromWCharArray(deviceName.c_str());
        m_devices->addItem(name);
    }

    auto deviceResolutions = m_videoCapture->getActiveDeviceResolutions();
    for (auto &deviceResolution : deviceResolutions)
    {
        QString resolution = QString::fromStdString(deviceResolution);
        m_resolutions->addItem(resolution);
    }

    connect(m_resolutions, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
            static_cast<void (WebcamWindow::*)(int)>(&WebcamWindow::changeResolution));
    connect(m_devices, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
            static_cast<void (WebcamWindow::*)(int)>(&WebcamWindow::changeDevice));
    connect(m_startButton, &QPushButton::released, this, &WebcamWindow::startCapture);
    connect(m_stopButton, &QPushButton::released, this, &WebcamWindow::stopCapture);
    connect(m_captureButton, &QPushButton::released, this, &WebcamWindow::incCaptureCnt);
    connect(m_captureThreeButton, &QPushButton::released, this, &WebcamWindow::incThreeCaptureCnt);
    //connect(m_flipButton, &QPushButton::released, this, &WebcamWindow::flipFrame);
    connect(m_browserButton, &QPushButton::clicked, this, &WebcamWindow::browse);
}

WebcamWindow::~WebcamWindow()
{
    m_aviWriter->close();
    delete m_aviWriter;
    m_devices->blockSignals(true);
    m_resolutions->blockSignals(true);
    m_videoCapture->stopCapture();
    delete m_videoCapture;
}

void WebcamWindow::processStillFrame(const unsigned char *data, int len, VideoDevice *device)
{
    if (!device || data == nullptr || len <= 0)
    {
        return;
    }

    VideoDevice::Properties prop = device->getCurrentProperties();

    long width  = prop.width;
    long height = prop.height;

    m_makeQImage = getQImageMaker(prop.pixelFormat);

    QImage newFrame(m_makeQImage(data, len, width, height));
    writeQImageToFile(newFrame);
}

static std::string getTimeString(bool bLocal = true, bool bIncludeMS = true) {
    auto tNow = std::chrono::system_clock::now();
    //auto tmNow = std::chrono::system_clock::to_time_t(tNow);
    auto tSeconds = std::chrono::duration_cast<std::chrono::seconds>(tNow.time_since_epoch());
    auto secNow = tSeconds.count();
    tm tmNow;
    if (bLocal) {
        localtime_s(&tmNow, &secNow);
    }
    else {
        gmtime_s(&tmNow, &secNow);
    }

    std::ostringstream oss;
    oss << std::put_time(&tmNow, "%Y-%m-%d %H %M %S");
    if (bIncludeMS) {
        auto tMilli = std::chrono::duration_cast<std::chrono::milliseconds>(tNow.time_since_epoch());
        auto ms = tMilli - tSeconds;
        oss << " " << std::setfill('0') << std::setw(3) << ms.count();
    }

    return oss.str();
}

void WebcamWindow::writeQImageToFile(const QImage& img)
{
    QString name    = m_name->text();
    if (name.isEmpty())
    {
        name = tr("unknown");
    }
    QString app_dir = m_appDirPath;
    QDir dir;
    if (!dir.exists(app_dir))
    {
        dir.mkpath(app_dir);
    }
    auto pt         = getTimeString();
    std::stringstream ss;
    ss << pt << ".jpg";
    std::string filename;
    while (!ss.eof())
    {
        std::string res;
        ss >> res;
        filename += res;
    }
    app_dir = app_dir + "/" + name + " " + filename.c_str();
    img.save(app_dir, "JPG");
}

void WebcamWindow::processFrame(const unsigned char *data, int len, VideoDevice *device)
{
    if (!device || data == nullptr || len <= 0)
    {
        return;
    }

    VideoDevice::Properties prop = device->getCurrentProperties();

    long width  = prop.width;
    long height = prop.height;

    m_makeQImage = getQImageMaker(prop.pixelFormat);

    QImage newFrame(m_makeQImage(data, len, width, height));
    m_frameMutex.lock();
    if (m_photoCount)
    {
        writeQImageToFile(newFrame);
        --m_photoCount;
    }
    long vp_width = m_viewport->width();
    long vp_height = m_viewport->height();
    //m_frame = newFrame.mirrored(device->getCurrentProperties().isFlippedHorizontal, device->getCurrentProperties().isFlippedVertical || m_isFlipped);
    m_frame = newFrame.scaled(vp_width, vp_height);
    m_frameMutex.unlock();

    QMetaObject::invokeMethod(this, "presentFrame", Qt::QueuedConnection);
}

void WebcamWindow::resizeEvent(QResizeEvent* ev)
{
    m_frameMutex.lock();
    QMainWindow::resizeEvent(ev);
    m_frameMutex.unlock();
}

void WebcamWindow::incCaptureCnt()
{
    m_frameMutex.lock();
    ++m_photoCount;
    m_frameMutex.unlock();
}

void WebcamWindow::incThreeCaptureCnt()
{
    m_frameMutex.lock();
    m_photoCount += 3;
    m_frameMutex.unlock();
}

void WebcamWindow::presentFrame()
{
    m_frameMutex.lock();
    m_viewport->setPixmap(QPixmap::fromImage(m_frame));
    m_frameMutex.unlock();
    //adjustSize();
    m_viewport->repaint();
}

void WebcamWindow::changeResolution(int resolutionNum)
{
    bool wasCapturing = m_isCapturing;
    stopCapture();
    m_videoCapture->changeActiveDeviceResolution(resolutionNum);
    //adjustSize();
    if (wasCapturing)
    {
        startCapture();
    }
}

void WebcamWindow::changeDevice(int deviceNum)
{
    bool wasCapturing = m_isCapturing;
    stopCapture();

    m_videoCapture->changeActiveDevice(deviceNum);

    m_resolutions->clear();
    auto deviceResolutions = m_videoCapture->getActiveDeviceResolutions();
    for (auto &deviceResolution : deviceResolutions)
    {
        QString resolution = QString::fromStdString(deviceResolution);
        m_resolutions->addItem(resolution);
    }

    if (wasCapturing)
    {
        startCapture();
    }
}

void WebcamWindow::flipFrame()
{
    m_isFlipped = !m_isFlipped;
}

void WebcamWindow::startCapture()
{
    m_isStop = false;
    m_startButton->setEnabled(false);
    m_stopButton->setEnabled(true);
    m_captureButton->setEnabled(true);
    m_captureThreeButton->setEnabled(true);
    //m_flipButton->setEnabled(true);

    if (m_videoCapture->startCapture())
    {
        m_isCapturing = true;
    }
}

void WebcamWindow::stopCapture()
{
    m_isStop = true;
    m_startButton->setEnabled(true);
    m_stopButton->setEnabled(false);
    m_captureButton->setEnabled(false);
    m_captureThreeButton->setEnabled(false);
    //m_flipButton->setEnabled(false);

    if (m_videoCapture->stopCapture())
    {
        m_isCapturing = false;
    }
    m_frame.fill(Qt::GlobalColor::white);
    presentFrame();
}

void WebcamWindow::browse()
{
    QString directory = QFileDialog::getExistingDirectory(this, tr("Get output directory"), QDir::currentPath());
    if (!directory.isEmpty())
    {
        m_appDirPath = directory;
        m_directory->setText(directory);
        m_settings.setValue("mainwindow/directory", m_appDirPath);
    }
}
